import { useSettingStore } from '@/store/bpmnProcess/settingStore'
import { getBusinessObject, is, isAny } from 'bpmn-js/lib/util/ModelUtil'
import { useModelerStore } from '@/store/bpmnProcess/modelerStore'
import { createModdleElement, getExtensionElementsList } from './bpmnPropertyUtils/extensionElements'
import { without } from 'min-dash'

export function getInputParameters(element) {
  return getParameters(element, 'inputParameters')
}

export function addInputParameter(element, options) {
  const prefix = useSettingStore().editor.processEngine
  AddParameterCmd(element, `${prefix}:InputParameter`, options)
}

export function editInputParameter(element, options, index) {
  const prefix = useSettingStore().editor.processEngine
  editInputOutputParameter(element, options, index, `${prefix}:InputParameter`)
}

export function removeInputParameters(element, parameter) {
  removeInputOutputParameters(element, parameter, 'inputParameters')
}

export function getOutputParameters(element) {
  return getParameters(element, 'outputParameters')
}

export function addOutputParameter(element, options) {
  const prefix = useSettingStore().editor.processEngine
  AddParameterCmd(element, `${prefix}:OutputParameter`, options)
}

export function editOutputParameter(element, options, index) {
  const prefix = useSettingStore().editor.processEngine
  editInputOutputParameter(element, options, index, `${prefix}:OutputParameter`)
}

export function removeOutputParameters(element, parameter) {
  removeInputOutputParameters(element, parameter, 'outputParameters')
}

function getParameters(element, prop) {
  const inputOutput = getInputOutput(element)
  return (inputOutput && inputOutput.get(prop)) || []
}

export function getInputOutputType(parameter) {
  const definitionTypes = {
    'camunda:Map': 'map',
    'camunda:List': 'list',
    'camunda:Script': 'script'
  }
  let type = 'stringOrExpression'
  const definition = parameter.get('definition')
  if (typeof definition !== 'undefined') {
    type = definitionTypes[definition.$type]
  }
  return type
}

export function editInputOutputParameter(element, options, index, type) {
  const inputOutput = getInputOutput(element)
  const inputParameters = getInputParameters(element)
  const currentParameter = inputParameters[index]
  currentParameter.name = options.name
  if (currentParameter.value) {
    delete currentParameter.value
  }
  if (currentParameter.definition) {
    delete currentParameter.definition
  }
  updateParameter(element, type, inputOutput, options, currentParameter, index)
}

function AddParameterCmd(element, type, options) {
  const prefix = useSettingStore().editor.processEngine
  const modeling = useModelerStore().getModeling
  const businessObject = getBusinessObject(element)
  let extensionElements = businessObject.get('extensionElements')

  // (1) ensure extension elements
  if (!extensionElements) {
    extensionElements = createModdleElement('bpmn:ExtensionElements', { values: [] }, businessObject)
    modeling.updateModdleProperties(element, businessObject, { extensionElements })
  }

  // (2) ensure inputOutput
  let inputOutput = getInputOutput(element)
  if (!inputOutput) {
    const parent = extensionElements
    inputOutput = createModdleElement(`${prefix}:InputOutput`, { inputParameters: [], outputParameters: [] }, parent)
    modeling.updateModdleProperties(element, extensionElements, { values: [...extensionElements.get('values'), inputOutput] })
  }

  // (3) create + add parameter
  CreateParameterCmd(element, type, inputOutput, options)
}

export function removeInputOutputParameters(element, parameter, type) {
  const modeling = useModelerStore().getModeling
  const inputOutput = getInputOutput(element)
  if (!inputOutput) {
    return
  }
  const parameterList = inputOutput.get(type)
  const newValue = without(parameterList, parameter)
  modeling.updateModdleProperties(element, inputOutput, { [type]: newValue })
}

export function CreateParameterCmd(element, type, parent, options) {
  const newParameter = createModdleElement(type, { name: options.name }, parent)
  updateParameter(element, type, parent, options, newParameter, -1)
}

function updateParameter(element, type, parent, options, newParameter, index) {
  const prefix = useSettingStore().editor.processEngine
  const modeling = useModelerStore().getModeling
  const isInput = type === `${prefix}:InputParameter`
  const properties = {
    value: undefined,
    definition: undefined
  }
  if (options.type === 'script') {
    properties.definition = createDefinitionElement(newParameter, `${prefix}:Script`)
    modeling.updateModdleProperties(element, newParameter, properties)
    if (options.script) {
      const script = newParameter.get('definition')
      const { scriptFormat, scriptType, value, resource } = options.script
      if (scriptType === 'inline') {
        modeling.updateModdleProperties(element, script, { scriptFormat, value })
      } else {
        modeling.updateModdleProperties(element, script, { scriptFormat, resource })
      }
    }
  } else if (options.type === 'list') {
    properties.definition = createDefinitionElement(newParameter, `${prefix}:List`)
    modeling.updateModdleProperties(element, newParameter, properties)
    if (options.definition && options.definition.items && options.definition.items.length > 0) {
      const list = newParameter.get('definition')
      options.definition.items.forEach((item) => {
        const newValue = createModdleElement(`${prefix}:Value`, item, newParameter)
        modeling.updateModdleProperties(element, list, { items: [...list.get('items'), newValue] })
      })
    }
  } else if (options.type === 'map') {
    properties.definition = createDefinitionElement(newParameter, `${prefix}:Map`)
    modeling.updateModdleProperties(element, newParameter, properties)
    if (options.definition && options.definition.entries && options.definition.entries.length > 0) {
      const map = newParameter.get('definition')
      options.definition.entries.forEach((item) => {
        const entry = createModdleElement(`${prefix}:Entry`, item, newParameter)
        modeling.updateModdleProperties(element, map, { entries: [...map.get('entries'), entry] })
      })
    }
  } else if (options.type === 'stringOrExpression') {
    properties.value = options.stringOrExpression
    modeling.updateModdleProperties(element, newParameter, properties)
  }

  const propertyName = isInput ? 'inputParameters' : 'outputParameters'
  if (index === -1) {
    modeling.updateModdleProperties(element, parent, {
      [propertyName]: [...parent.get(propertyName), newParameter]
    })
  } else {
    parent.get(propertyName)[index] = newParameter
    modeling.updateModdleProperties(element, parent, {
      [propertyName]: parent.get(propertyName)
    })
  }
}

const createDefinitionElement = (parameter, type) => {
  return createModdleElement(type, {}, parameter)
}

function getElements(businessObject, type, property?) {
  const elements = getExtensionElementsList(businessObject, type)
  return !property ? elements : (elements[0] || {})[property] || []
}

export function getInputOutput(element) {
  const prefix = useSettingStore().editor.processEngine
  if (is(element, `${prefix}:Connector`)) {
    return element.get('inputOutput')
  }
  const businessObject = getBusinessObject(element)
  return (getElements(businessObject, `${prefix}:InputOutput`) || [])[0]
}

function isInputOutputSupported(element) {
  const businessObject = getBusinessObject(element)
  return is(businessObject, 'bpmn:FlowNode') && !(isAny(businessObject, ['bpmn:StartEvent', 'bpmn:BoundaryEvent', 'bpmn:Gateway']) || (is(businessObject, 'bpmn:SubProcess') && businessObject.get('triggeredByEvent')))
}
export function areInputParametersSupported(element) {
  return isInputOutputSupported(element)
}
export function areOutputParametersSupported(element) {
  const businessObject = getBusinessObject(element)
  return isInputOutputSupported(element) && !is(businessObject, 'bpmn:EndEvent') && !businessObject.loopCharacteristics
}

export function getAssignmentTypeList() {
  return [
    { label: '列表', value: 'list' },
    { label: '映射', value: 'map' },
    { label: '脚本', value: 'script' },
    { label: '字符串或表达式', value: 'stringOrExpression' }
  ]
}
export function getAssignmentTypeObject() {
  return {
    list: '列表',
    map: '映射',
    script: '脚本',
    stringOrExpression: '字符串或表达式'
  }
}
